package com.cxy.common.util;

import com.cxy.common.config.ConfigUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.regex.Pattern;

/*********************************************************
 * 文件名称：NetUtils.java
 * 系统名称：CXY自定义系统
 * 模块名称：com.cxy.common.util
 * 功能说明：网络相关工具类
 * 开发人员 @author：caoxy31978
 * 开发时间 @date：2023/6/29 14:30
 * 修改记录：程序版本  修改日期  修改人员  修改单号  修改说明
 *********************************************************/
@Slf4j
public class NetUtils {
    
    private static final int RND_PORT_START = 30000;
    private static final int RND_PORT_RANGE = 10000;
    private static final Random RANDOM = new Random(System.currentTimeMillis());
    private static volatile InetAddress LOCAL_ADDRESS = null;
    private static volatile String HOST_FILTER = null;
    private static final Pattern IP_PATTERN = Pattern.compile("\\d{1,3}(\\.\\d{1,3}){3,5}$");
    private static final Pattern LOCAL_IP_PATTERN = Pattern.compile("127(\\.\\d{1,3}){3}$");
    private static final String SPLIT_IPV4_CHARECTER = "\\.";
    private static final String SPLIT_IPV6_CHARECTER = ":";
    private static Map<String, CIDRUtils> cidrUtilsMap = new HashMap();
    private static String cachedIpAddress;
    private static String cachedSocketIpAddress;

    public static final String ANYHOST_VALUE = "0.0.0.0";
    public static final String LOCALHOST_KEY = "localhost";
    public static final String LOCALHOST_VALUE = "127.0.0.1";
    public static final Pattern COMMA_SPLIT_PATTERN = Pattern.compile("\\s*[,]+\\s*");
    public static final String UNKNOWN = "unknown";
    public static final String CLASSPATH_URL_PREFIX = "classpath:";
    

    public static int getRandomPort() {
        return RND_PORT_START + RANDOM.nextInt(RND_PORT_RANGE);
    }

    public static int getAvailablePort() {
        try(ServerSocket ss = new ServerSocket()) {
            ss.bind(null);
            return ss.getLocalPort();
        } catch (IOException var12) {
            return getRandomPort();
        }
    }

    public static String getLocalHost() {
        InetAddress address = getLocalAddress();
        return address == null ? LOCALHOST_KEY : address.getHostAddress();
    }

    public static String getLocalHost(String host) {
        HOST_FILTER = host;
        return getLocalHost();
    }

    public static InetAddress getLocalAddress() {
        if (LOCAL_ADDRESS != null) {
            return LOCAL_ADDRESS;
        } else {
            InetAddress localAddress = getLocalAddress0();
            LOCAL_ADDRESS = localAddress;
            return localAddress;
        }
    }

    private static String getHostFilter() {
        if (HOST_FILTER != null) {
            return HOST_FILTER;
        } else {
            HOST_FILTER = ConfigUtils.getAppHost();
            return HOST_FILTER;
        }
    }

    private static InetAddress getLocalAddress0() {
        InetAddress localAddress = null;

        try {
            localAddress = InetAddress.getLocalHost();
            if (localAddress instanceof Inet6Address) {
                Inet6Address address = (Inet6Address)localAddress;
                if (isValidV6Address(address)) {
                    return normalizeV6Address(address);
                }
            } else if (isValidAddress(localAddress)) {
                return localAddress;
            }
        } catch (Throwable var7) {
            log.warn("Failed to retriving ip address, " + var7.getMessage(), var7);
        }

        try {
            Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
            if (interfaces != null) {
                while(interfaces.hasMoreElements()) {
                    try {
                        NetworkInterface network = (NetworkInterface)interfaces.nextElement();
                        Enumeration<InetAddress> addresses = network.getInetAddresses();
                        if (addresses != null) {
                            while(addresses.hasMoreElements()) {
                                try {
                                    InetAddress address = (InetAddress)addresses.nextElement();
                                    if (address instanceof Inet6Address) {
                                        Inet6Address v6Address = (Inet6Address)address;
                                        if (isValidV6Address(v6Address)) {
                                            return normalizeV6Address(v6Address);
                                        }
                                    } else if (isValidAddress(address)) {
                                        return address;
                                    }
                                } catch (Throwable var6) {
                                    log.warn("Failed to retriving ip address, " + var6.getMessage(), var6);
                                }
                            }
                        }
                    } catch (Throwable var8) {
                        log.warn("Failed to retriving ip address, " + var8.getMessage(), var8);
                    }
                }
            }
        } catch (Throwable var9) {
            log.warn("Failed to retriving ip address, " + var9.getMessage(), var9);
        }

        log.error("Could not get local host ip address, will use local host instead.");
        return localAddress;
    }

    static boolean isValidV6Address(Inet6Address address) {
        String name = normalizeV6Address(address).getHostAddress();
        if ("0:0:0:0:0:0:0:1".equals(address.getHostAddress())) {
            return false;
        } else {
            String[] regexs = COMMA_SPLIT_PATTERN.split(getHostFilter());
            boolean isMatched = false;
            if (name != null) {
                String[] var4 = regexs;
                int var5 = regexs.length;

                for(int var6 = 0; var6 < var5; ++var6) {
                    String regex = var4[var6];
                    if (Pattern.compile("^" + regex + "$", 2).matcher(name).matches()) {
                        isMatched = true;
                    }
                }
            }

            if (!isMatched) {
                return false;
            } else {
                boolean preferIpv6 = Boolean.getBoolean("java.net.preferIPv6Addresses");
                if (!preferIpv6) {
                    return false;
                } else {
                    try {
                        return address.isReachable(100);
                    } catch (IOException var8) {
                        return false;
                    }
                }
            }
        }
    }

    static InetAddress normalizeV6Address(Inet6Address address) {
        String addr = address.getHostAddress();
        int i = addr.lastIndexOf(37);
        if (i > 0) {
            try {
                return InetAddress.getByName(addr.substring(0, i) + '%' + address.getScopeId());
            } catch (UnknownHostException var4) {
                log.debug("Unknown IPV6 address: ", var4);
            }
        }

        return address;
    }

    private static boolean isValidAddress(InetAddress address) {
        if (address != null && !address.isLoopbackAddress()) {
            String name = address.getHostAddress();
            String[] regexs = COMMA_SPLIT_PATTERN.split(getHostFilter().replace(".", SPLIT_IPV4_CHARECTER).replace("*", "(.*)"));
            boolean isOne = false;
            if (name != null) {
                String[] var4 = regexs;
                int var5 = regexs.length;

                for(int var6 = 0; var6 < var5; ++var6) {
                    String regex = var4[var6];
                    if (Pattern.compile("^" + regex + "$", 2).matcher(name).matches()) {
                        isOne = true;
                    }
                }
            }

            return name != null && !ANYHOST_VALUE.equals(name) && !LOCALHOST_KEY.equals(name) && IP_PATTERN.matcher(name).matches() && isOne;
        } else {
            return false;
        }
    }

    public static boolean isInvalidLocalHost(String host) {
        String[] regexs = COMMA_SPLIT_PATTERN.split(getHostFilter().replace(".", SPLIT_IPV4_CHARECTER).replace("*", "(.*)"));
        boolean isOne = false;
        if (host != null) {
            String[] var3 = regexs;
            int var4 = regexs.length;

            for(int var5 = 0; var5 < var4; ++var5) {
                String regex = var3[var5];
                if (Pattern.compile("^" + regex + "$", 2).matcher(host).matches()) {
                    isOne = true;
                }
            }
        }

        return host == null || host.length() == 0 || host.equalsIgnoreCase(LOCALHOST_KEY) || host.equals(ANYHOST_VALUE) || LOCAL_IP_PATTERN.matcher(host).matches() || !isOne;
    }

    public static String getCachedSocketIpAddress(String host) {
        return null == cachedSocketIpAddress ? host : cachedSocketIpAddress;
    }

    public static void setCachedSocketIpAddress(String cachedSocketIpAddress) {
        NetUtils.cachedSocketIpAddress = cachedSocketIpAddress;
    }

    public static boolean isInSameNetwork(String cidrNotation, String address) {
        if (!StringUtils.isEmpty(cidrNotation) && !StringUtils.isEmpty(address)) {
            try {
                return ((CIDRUtils)cidrUtilsMap.get(cidrNotation)).isInRange(address);
            } catch (Exception var3) {
                return false;
            }
        } else {
            return false;
        }
    }

    public static void initCIDRUtilsMap(String networkPriority) throws UnknownHostException {
        if (StringUtils.isNotEmpty(networkPriority)) {
            String[] cidrNotations = networkPriority.split(",");
            String[] var2 = cidrNotations;
            int var3 = cidrNotations.length;

            for(int var4 = 0; var4 < var3; ++var4) {
                String cidrNotation = var2[var4];
                if (cidrNotation.contains("/")) {
                    CIDRUtils util = new CIDRUtils(cidrNotation);
                    cidrUtilsMap.put(cidrNotation, util);
                }
            }
        }

    }

    public static boolean matchIpExpression(String pattern, String host, int port) throws UnknownHostException {
        if (pattern.contains("/")) {
            CIDRUtils utils = new CIDRUtils(pattern);
            return utils.isInRange(host);
        } else {
            return matchIpRange(pattern, host, port);
        }
    }

    public static boolean matchIpRange(String pattern, String host, int port) throws UnknownHostException {
        if (pattern != null && host != null) {
            pattern = pattern.trim();
            if (!"*.*.*.*".equals(pattern) && !"*".equals(pattern)) {
                InetAddress inetAddress = InetAddress.getByName(host);
                boolean isIpv4 = isValidV4Address(inetAddress);
                String[] hostAndPort = getPatternHostAndPort(pattern, isIpv4);
                if (hostAndPort[1] != null && !hostAndPort[1].equals(String.valueOf(port))) {
                    return false;
                } else {
                    pattern = hostAndPort[0];
                    String splitCharacter = SPLIT_IPV4_CHARECTER;
                    if (!isIpv4) {
                        splitCharacter = SPLIT_IPV6_CHARECTER;
                    }

                    String[] mask = pattern.split(splitCharacter);
                    checkHostPattern(pattern, mask, isIpv4);
                    host = inetAddress.getHostAddress();
                    String[] ipAddress = host.split(splitCharacter);
                    if (pattern.equals(host)) {
                        return true;
                    } else if (!ipPatternContainExpression(pattern)) {
                        InetAddress patternAddress = InetAddress.getByName(pattern);
                        return patternAddress.getHostAddress().equals(host);
                    } else {
                        for(int i = 0; i < mask.length; ++i) {
                            if (!"*".equals(mask[i]) && !mask[i].equals(ipAddress[i])) {
                                if (mask[i].contains("-")) {
                                    String[] rangeNumStrs = mask[i].split("-");
                                    if (rangeNumStrs.length != 2) {
                                        throw new IllegalArgumentException("There is wrong format of ip Address: " + mask[i]);
                                    }

                                    Integer min = getNumOfIpSegment(rangeNumStrs[0], isIpv4);
                                    Integer max = getNumOfIpSegment(rangeNumStrs[1], isIpv4);
                                    Integer ip = getNumOfIpSegment(ipAddress[i], isIpv4);
                                    if (ip < min || ip > max) {
                                        return false;
                                    }
                                } else if ((!"0".equals(ipAddress[i]) || !"0".equals(mask[i]) && !"00".equals(mask[i]) && !"000".equals(mask[i]) && !"0000".equals(mask[i])) && !mask[i].equals(ipAddress[i])) {
                                    return false;
                                }
                            }
                        }

                        return true;
                    }
                }
            } else {
                return true;
            }
        } else {
            throw new IllegalArgumentException("Illegal Argument pattern or hostName. Pattern:" + pattern + ", Host:" + host);
        }
    }

    static boolean isValidV4Address(InetAddress address) {
        if (address != null && !address.isLoopbackAddress()) {
            String name = address.getHostAddress();
            boolean result = name != null && IP_PATTERN.matcher(name).matches() && !ANYHOST_VALUE.equals(name) && !LOCALHOST_VALUE.equals(name);
            return result;
        } else {
            return false;
        }
    }

    private static String[] getPatternHostAndPort(String pattern, boolean isIpv4) {
        String[] result = new String[2];
        int end;
        if (pattern.startsWith("[") && pattern.contains("]:")) {
            end = pattern.indexOf("]:");
            result[0] = pattern.substring(1, end);
            result[1] = pattern.substring(end + 2);
            return result;
        } else if (pattern.startsWith("[") && pattern.endsWith("]")) {
            result[0] = pattern.substring(1, pattern.length() - 1);
            result[1] = null;
            return result;
        } else if (isIpv4 && pattern.contains(SPLIT_IPV6_CHARECTER)) {
            end = pattern.indexOf(SPLIT_IPV6_CHARECTER);
            result[0] = pattern.substring(0, end);
            result[1] = pattern.substring(end + 1);
            return result;
        } else {
            result[0] = pattern;
            return result;
        }
    }

    private static void checkHostPattern(String pattern, String[] mask, boolean isIpv4) {
        if (!isIpv4) {
            if (mask.length != 8 && ipPatternContainExpression(pattern)) {
                throw new IllegalArgumentException("If you config ip expression that contains '*' or '-', please fill qulified ip pattern like 234e:0:4567:0:0:0:3d:*. ");
            }

            if (mask.length != 8 && !pattern.contains("::")) {
                throw new IllegalArgumentException("The host is ipv6, but the pattern is not ipv6 pattern : " + pattern);
            }
        } else if (mask.length != 4) {
            throw new IllegalArgumentException("The host is ipv4, but the pattern is not ipv4 pattern : " + pattern);
        }

    }

    private static boolean ipPatternContainExpression(String pattern) {
        return pattern.contains("*") || pattern.contains("-");
    }

    private static Integer getNumOfIpSegment(String ipSegment, boolean isIpv4) {
        return isIpv4 ? Integer.parseInt(ipSegment) : Integer.parseInt(ipSegment, 16);
    }

    public static synchronized String getCachedIpAddress() {
        if (StringUtils.isNotEmpty(cachedIpAddress)) {
            return cachedIpAddress;
        } else {
            try {
                cachedIpAddress = InetAddress.getLocalHost().getHostAddress();
            } catch (UnknownHostException var1) {
                log.warn(var1.getMessage(), var1);
            }

            return cachedIpAddress;
        }
    }
}
